/**HEADER********************************************************************
* 
* Copyright (c) 2008-2009 Freescale Semiconductor;
* All Rights Reserved
*
* Copyright (c) 2004-2008 Embedded Access Inc.;
* All Rights Reserved
*
* Copyright (c) 1989-2008 ARC International;
* All Rights Reserved
*
*************************************************************************** 
*
* THIS SOFTWARE IS PROVIDED BY FREESCALE "AS IS" AND ANY EXPRESSED OR 
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  
* IN NO EVENT SHALL FREESCALE OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 
* THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************
*
* $FileName: serl_int_st16550.c$
* $Version : 3.5.1.0$
* $Date    : Feb-24-2010$
*
* Comments:
*
*   This file contains the  low level functions for the interrupt driven
*   serial I/O for the 16550 compatible UART.
*
*END*********************************************************************/

#include "mqx.h"
#include "bsp.h"
#include "io_prv.h"
#include "charq.h"
#include "fio_prv.h"
#include "serinprv.h"

extern uint_32 _st16550_serial_int_init(IO_SERIAL_INT_DEVICE_STRUCT_PTR, char_ptr);
extern uint_32 _st16550_serial_int_deinit(ST16550_SERIAL_INIT_STRUCT_PTR,
   ST16550_SERIAL_INFO_STRUCT_PTR);
extern uint_32 _st16550_serial_int_enable(ST16550_SERIAL_INFO_STRUCT_PTR);
extern void _st16550_serial_int_isr(pointer);
extern void _st16550_serial_int_putc(IO_SERIAL_INT_DEVICE_STRUCT_PTR, char);
extern uint_32 _st16550_serial_ioctl(ST16550_SERIAL_INFO_STRUCT_PTR,uint_32,
  pointer);

extern uint_32 _st16550_serial_polled_init(
   ST16550_SERIAL_INIT_STRUCT_PTR, pointer _PTR_, char_ptr);
extern uint_32 _st16550_serial_polled_deinit(
   ST16550_SERIAL_INIT_STRUCT_PTR, ST16550_SERIAL_INFO_STRUCT_PTR );


/*FUNCTION*-------------------------------------------------------------------
* 
* Function Name    : _st16550_serial_int_install
* Returned Value   : uint_32 a task error code or MQX_OK
* Comments         :
*    Install an interrupt driven serial device.
*
*END*----------------------------------------------------------------------*/

uint_32 _st16550_serial_int_install
   (   
      /* [IN] A string that identifies the device for fopen */
      char_ptr identifier,
  
      /* [IN] The I/O init data pointer */
      pointer  init_data_ptr,
      
      /* [IN] The I/O queue size to use */
      uint_32  queue_size
   )
{ /* Body */

/* Start CR 1026 */
   return _io_serial_int_install(identifier,
      (_mqx_uint (_CODE_PTR_)(pointer, char _PTR_))
         _st16550_serial_int_init, 
      (_mqx_uint (_CODE_PTR_)(pointer))
         _st16550_serial_int_enable, 
      (_mqx_uint (_CODE_PTR_)(pointer,pointer))
         _st16550_serial_int_deinit, 
      (void    (_CODE_PTR_)(pointer, char))
         _st16550_serial_int_putc, 
      (_mqx_uint (_CODE_PTR_)(pointer, _mqx_uint, pointer))
         _st16550_serial_ioctl, 
      init_data_ptr, queue_size);
/* End CR 1026 */

} /* Endbody */


/*FUNCTION****************************************************************
* 
* Function Name    : _st16550_serial_int_init
* Returned Value   : uint_32 a task error code or MQX_OK
* Comments         :
*    This function initializes the UART in interrupt mode.
*
*END*********************************************************************/

uint_32 _st16550_serial_int_init
   (
      /* [IN] the interrupt I/O initialization information */
      IO_SERIAL_INT_DEVICE_STRUCT_PTR int_io_dev_ptr,
       
      /* [IN] the rest of the name of the device opened */
      char                      _PTR_ open_name_ptr
   )
{ /* Body */
   ST16550_SERIAL_INIT_STRUCT_PTR io_init_ptr;
   ST16550_SERIAL_INFO_STRUCT_PTR io_info_ptr;
   volatile ST16550_UART_STRUCT _PTR_ uart_ptr;
   uint_32                        result;

   io_init_ptr = int_io_dev_ptr->DEV_INIT_DATA_PTR;

   result = _st16550_serial_polled_init((pointer)io_init_ptr, 
      &int_io_dev_ptr->DEV_INFO_PTR, open_name_ptr);
   if (result != MQX_OK) {
      return(result);
   } /* Endif */

   io_info_ptr = int_io_dev_ptr->DEV_INFO_PTR;
   io_info_ptr->INT_MODE = IO_SERIAL_INT_MODE_INTERRUPT;

   io_info_ptr->VECTOR  = io_init_ptr->VECTOR;

   /* Setup the interrupt */
   io_info_ptr->OLD_ISR_DATA = _int_get_isr_data(io_info_ptr->VECTOR);
   io_info_ptr->OLD_ISR_EXCEPTION_HANDLER = 
      _int_get_exception_handler(io_info_ptr->VECTOR);
   
   io_info_ptr->OLD_ISR = 
      _int_install_isr(io_info_ptr->VECTOR, _st16550_serial_int_isr, 
      int_io_dev_ptr);
      
   uart_ptr = io_info_ptr->UART_PTR;
   uart_ptr->IER = ST16550_UART_IER_DATA_AVAIL |
              ST16550_UART_IER_XMIT_EMPTY |
              ST16550_UART_IER_LINE_STATUS |
              ST16550_UART_IER_MDM_STATUS;

   return(MQX_OK);

} /* Endbody */


/*FUNCTION****************************************************************
* 
* Function Name    : _st16550_serial_int_deinit
* Returned Value   : uint_32 a task error code or MQX_OK
* Comments         :
*    This function de-initializes the UART in interrupt mode.
*
*END*********************************************************************/

uint_32 _st16550_serial_int_deinit
   (
      /* [IN] the interrupt I/O initialization information */
      ST16550_SERIAL_INIT_STRUCT_PTR io_init_ptr,
       
      /* [IN] the address of the device specific information */
      ST16550_SERIAL_INFO_STRUCT_PTR io_info_ptr
   )
{ /* Body */
   volatile ST16550_UART_STRUCT _PTR_   uart_ptr;
  
   uart_ptr  = io_info_ptr->UART_PTR;

   _st16550_serial_polled_deinit(io_init_ptr, io_info_ptr);

   /* Disable interrupts */
   if (io_info_ptr->INIT.INT_DISABLE) {
      (io_info_ptr->INIT.INT_DISABLE)(io_init_ptr->VECTOR);
   } /* Endif */

   uart_ptr->MCR = 0; /* tell chip to NOT interrupt */   
   _BSP_IO_EIEIO;
   uart_ptr->IER = 0; /* Reset interrupts */
   _BSP_IO_EIEIO;

   /* Restore old vector */
   _int_install_isr(io_init_ptr->VECTOR, io_info_ptr->OLD_ISR, 
      io_info_ptr->OLD_ISR_DATA);

   return(MQX_OK);

} /* Endbody */


/*FUNCTION****************************************************************
* 
* Function Name    : _st16550_serial_int_enable
* Returned Value   : uint_32 a task error code or MQX_OK
* Comments         :
*    This function enables the UART interrupts mode.
*
*END*********************************************************************/

uint_32 _st16550_serial_int_enable
   (
      /* [IN] the address of the device specific information */
      ST16550_SERIAL_INFO_STRUCT_PTR io_info_ptr
   )
{ /* Body */
   volatile ST16550_UART_STRUCT _PTR_   uart_ptr;
   volatile uchar                       tmp;

   uart_ptr  = io_info_ptr->UART_PTR;

   /* Enable interrupts */
   if (io_info_ptr->INIT.INT_ENABLE) {
      (io_info_ptr->INIT.INT_ENABLE)(io_info_ptr->VECTOR);
   } /* Endif */

   io_info_ptr->CONTROL_BITS |= ST16550_UART_MCR_IRQ_ENABLE;
   uart_ptr->MCR = io_info_ptr->CONTROL_BITS;
   _BSP_IO_EIEIO;

   uart_ptr->IER = ST16550_UART_IER_DATA_AVAIL |
                   ST16550_UART_IER_XMIT_EMPTY |
                   ST16550_UART_IER_LINE_STATUS |
                   ST16550_UART_IER_MDM_STATUS;
   _BSP_IO_EIEIO;

   /* Clear IIR */
   tmp = uart_ptr->IIR;
   _BSP_IO_EIEIO;
   
   /* Clear receive register */
   tmp = uart_ptr->DATA;
   _BSP_IO_EIEIO;
   
   /* Clear line status */
   tmp = uart_ptr->LSR;
   _BSP_IO_EIEIO;
   
   /* Clear modem status */
   tmp = uart_ptr->MSR;
   _BSP_IO_EIEIO;
   
   return(MQX_OK);

} /* Endbody */


/*FUNCTION****************************************************************
* 
* Function Name    : _st16550_serial_int_isr
* Returned Value   : none
* Comments         : 
*   interrupt handler for the serial I/O interrupts.
*
*************************************************************************/

void _st16550_serial_int_isr
   (
      /* [IN] the address of the device specific information */
      pointer parameter
   )
{ /* Body */
   IO_SERIAL_INT_DEVICE_STRUCT_PTR      int_io_dev_ptr = parameter;
   ST16550_SERIAL_INFO_STRUCT_PTR       io_info_ptr;
   volatile ST16550_UART_STRUCT _PTR_   uart_ptr;
   uchar                                lsr, iir, dsr;
   _mqx_int                             c;
   boolean                              work;
   
   io_info_ptr = int_io_dev_ptr->DEV_INFO_PTR;
   uart_ptr    = io_info_ptr->UART_PTR;

   ++io_info_ptr->INTERRUPTS;
   
   /* Perform start of interrupt handling */
  
   work = TRUE;
   while (work) {
      lsr  = uart_ptr->LSR;
      iir = (uchar)uart_ptr->IIR;
      dsr  = (uchar)uart_ptr->DSR;
      _BSP_IO_EIEIO;
      
      work = FALSE;
      switch (iir & 0x0f) {
         case 0x00:  /* CTS changed */
            if (uart_ptr->MSR & ST16550_UART_MSR_CTS) {
               int_io_dev_ptr->MUST_STOP_OUTPUT = FALSE;
            }
            else {
               int_io_dev_ptr->MUST_STOP_OUTPUT = TRUE;
            }
            break;
         case 0x01:  /* Nothing to do */
            break;
         case 0x02:  /* Tx Empty */
            break;
         case 0x04:  /* Rx available */
            break;
         case 0x0C:  /* Rx timeout */
            break;
         case 0x06:  /* Special Condition */
            if (lsr & (uchar)ST16550_UART_LSR_BREAKRCVD) {
               ++io_info_ptr->RX_BREAKS;
              /* Toss the extra data */
              c = (_mqx_int)uart_ptr->DATA;
              _BSP_IO_EIEIO;
            } /* Endif */
            if (lsr & (uchar)(ST16550_UART_LSR_OVERRUN | 
              ST16550_UART_LSR_PARITYERR | ST16550_UART_LSR_FRAMEERR)) 
            {
               if (lsr & ST16550_UART_LSR_PARITYERR) {
                 ++io_info_ptr->RX_PARITY_ERRORS;
               } /* Endif */
               if (lsr & ST16550_UART_LSR_FRAMEERR) {
                  ++io_info_ptr->RX_FRAMING_ERRORS;
               } /* Endif */
               if (lsr & ST16550_UART_LSR_OVERRUN) {
                  ++io_info_ptr->RX_OVERRUNS;
               } /* Endif */
               /* Clear the error */
               io_info_ptr->FIFO_CONTROL_REG |= ST16550_UART_FCR_RFR;
               uart_ptr->IIR = (uchar)io_info_ptr->FIFO_CONTROL_REG;
               _BSP_IO_SYNC;
               io_info_ptr->FIFO_CONTROL_REG &= ~ST16550_UART_FCR_RFR;
               uart_ptr->IIR = (uchar)io_info_ptr->FIFO_CONTROL_REG;        
 
            }
            _BSP_IO_EIEIO;
            break;
         default:
            break;
      }

      if (int_io_dev_ptr->MUST_STOP_OUTPUT==FALSE) {
         if (!(dsr & ST16550_UART_DSR_TXRDY)) {
            c = _io_serial_int_nextc(int_io_dev_ptr);
            if (c != (-1)){
               work = TRUE;
               uart_ptr->DATA = (uchar)((unsigned)c);
               _BSP_IO_EIEIO;
               ++io_info_ptr->TX_CHARS;
            } 
         }
         else {  
            work = TRUE;
         }
      }

      if (lsr & ST16550_UART_LSR_DATA_RCVD) {
         c = (uchar)uart_ptr->DATA;
         _BSP_IO_EIEIO;
         ++io_info_ptr->RX_CHARS;
         if (!_io_serial_int_addc(int_io_dev_ptr, (char)c)) {
            io_info_ptr->RX_DROPPED_INPUT++;
         }
         else {
            ++io_info_ptr->RX_GOOD_CHARS;
         }
         work = TRUE;
      }
                  
      _BSP_IO_SYNC;

   }

   
} /* Endbody */


/*FUNCTION****************************************************************
* 
* Function Name    : _st16550_serial_int_putc
* Returned Value   : none
* Comments         :
*   This function is called to write out the first character, when
* the output serial device and output ring buffers are empty.
*
*END*********************************************************************/

void _st16550_serial_int_putc
   (
      /* [IN] the address of the device specific information */
      IO_SERIAL_INT_DEVICE_STRUCT_PTR   int_io_dev_ptr,

      /* [IN] the character to write out now */
      char                              c
   )
{ /* Body */
   ST16550_SERIAL_INFO_STRUCT_PTR       io_info_ptr;
   volatile ST16550_UART_STRUCT _PTR_   uart_ptr;
   uchar                                dsr;

   io_info_ptr = int_io_dev_ptr->DEV_INFO_PTR;
   uart_ptr    = io_info_ptr->UART_PTR;

   dsr = uart_ptr->DSR;
   _BSP_IO_EIEIO;

   while (dsr & ST16550_UART_DSR_TXRDY) { /* TXRDY set when fifo full */
      dsr = uart_ptr->DSR;
      _BSP_IO_EIEIO;
   } /* Endwhile */

   uart_ptr->DATA = c;
   _BSP_IO_EIEIO;
   
   ++io_info_ptr->TX_CHARS;

} /* Endbody */

/* EOF */
